import logging
import requests
import json
from odoo import http, _
from odoo.http import request
from odoo.exceptions import AccessDenied
from odoo.addons.web.controllers.home import Home

_logger = logging.getLogger(__name__)


class DingTalkAuth(http.Controller):
    """钉钉扫码登录控制器"""
    
    @http.route('/auth/dingtalk', type='http', auth='public', csrf=False)
    def dingtalk_auth(self, **kw):
        """钉钉登录入口 - 跳转到钉钉授权页面"""
        _logger.info("=" * 80)
        _logger.info("🔐 钉钉登录请求")
        _logger.info(f"参数: {kw}")
        
        # 检查是否已经登录
        if request.session.uid:
            _logger.info(f"✅ 用户已登录 (UID: {request.session.uid})，直接跳转到主页")
            return request.redirect('/web')
        
        # 获取钉钉配置
        try:
            config = request.env['dingtalk.config'].sudo().search([('active', '=', True)], limit=1)
            if not config:
                return request.render('dingtalk_integration.dingtalk_login_error', {
                    'error': '未找到钉钉配置，请联系管理员'
                })
            
            app_key = config.app_key  # 在新版API中作为 client_id
            
            # 调试：打印所有相关请求头
            _logger.info(f"请求头信息:")
            _logger.info(f"  Host: {request.httprequest.headers.get('Host', '')}")
            _logger.info(f"  X-Forwarded-Host: {request.httprequest.headers.get('X-Forwarded-Host', '')}")
            _logger.info(f"  X-Forwarded-Proto: {request.httprequest.headers.get('X-Forwarded-Proto', '')}")
            _logger.info(f"  url_root: {request.httprequest.url_root}")
            _logger.info(f"  url: {request.httprequest.url}")
            
            # 使用真实的公网域名作为回调地址
            # 如果在Odoo配置中设置了domain，使用配置的domain
            # 否则从HTTP请求中获取Host头
            if hasattr(config, 'callback_domain') and config.callback_domain:
                callback_domain = config.callback_domain.rstrip('/')
                _logger.info(f"使用配置中的回调域名: {callback_domain}")
            else:
                # 优先使用 X-Forwarded-Host
                host = request.httprequest.headers.get('X-Forwarded-Host', '')
                if not host:
                    host = request.httprequest.headers.get('Host', '')
                
                if host:
                    # 判断是否使用HTTPS
                    scheme = 'https' if request.httprequest.headers.get('X-Forwarded-Proto') == 'https' else 'http'
                    callback_domain = f"{scheme}://{host}"
                    _logger.info(f"从请求头获取回调域名: {callback_domain}")
                else:
                    callback_domain = request.httprequest.url_root.rstrip('/')
                    _logger.info(f"使用url_root作为回调域名: {callback_domain}")
            
            redirect_uri = callback_domain + '/auth/dingtalk/callback'
            
            # URL 编码
            import urllib.parse
            redirect_uri_encoded = urllib.parse.quote(redirect_uri, safe='')
            
            # 钉钉扫码登录URL（新版 OAuth 2.0）
            # 参考：https://open.dingtalk.com/document/orgapp/tutorial-obtaining-user-personal-information
            auth_url = (
                f"https://login.dingtalk.com/oauth2/auth?"
                f"client_id={app_key}&"
                f"response_type=code&"
                f"scope=openid&"
                f"state=STATE&"
                f"prompt=consent&"
                f"redirect_uri={redirect_uri_encoded}"
            )
            
            _logger.info(f"跳转到钉钉授权页面 (新版 OAuth 2.0): {auth_url}")
            _logger.info(f"回调地址: {redirect_uri}")
            
            # 使用 werkzeug 的 redirect 进行外部跳转
            from werkzeug.utils import redirect as werkzeug_redirect
            return werkzeug_redirect(auth_url, code=302)
            
        except Exception as e:
            _logger.error(f"❌ 钉钉登录失败: {str(e)}")
            # 返回简单的HTML错误页面，不使用模板
            error_html = f"""
            <!DOCTYPE html>
            <html>
            <head>
                <meta charset="utf-8"/>
                <title>钉钉登录失败</title>
                <link rel="stylesheet" href="/web/static/lib/bootstrap/dist/css/bootstrap.min.css"/>
            </head>
            <body style="background: #f5f5f5; padding-top: 100px;">
                <div class="container">
                    <div class="alert alert-danger text-center" style="max-width: 600px; margin: 0 auto; padding: 40px;">
                        <h3 style="color: #d9534f;">钉钉登录失败</h3>
                        <hr/>
                        <p style="font-size: 16px;">{str(e)}</p>
                        <hr/>
                        <a href="/web/login" class="btn btn-primary btn-lg">返回登录页面</a>
                    </div>
                </div>
            </body>
            </html>
            """
            return request.make_response(error_html, headers={'Content-Type': 'text/html'})
    
    @http.route('/auth/dingtalk/callback', type='http', auth='none', csrf=False)
    def dingtalk_callback(self, **kw):
        """钉钉登录回调"""
        _logger.info("=" * 80)
        _logger.info("🔙 钉钉登录回调")
        _logger.info(f"参数: {kw}")
        
        auth_code = kw.get('code')
        if not auth_code:
            error_msg = kw.get('error_description', '未获取到授权码')
            _logger.error(f"❌ {error_msg}")
            return request.render('dingtalk_integration.dingtalk_login_error', {
                'error': error_msg
            })
        
        try:
            # 获取钉钉配置
            config = request.env['dingtalk.config'].sudo().search([('active', '=', True)], limit=1)
            if not config:
                raise Exception('未找到钉钉配置')
            
            # 1. 通过临时授权码获取用户信息
            user_info = self._get_user_info_by_code(auth_code, config)
            if not user_info:
                raise Exception('无法获取用户信息')
            
            _logger.info(f"👤 用户信息: {user_info}")
            
            # 2. 查找或创建用户（优先用 unionid，因为更可靠）
            userid = user_info.get('userid')
            unionid = user_info.get('unionid')
            
            if not unionid:
                raise Exception('未获取到钉钉 UnionID')
            
            _logger.info(f"🔍 查找用户: unionid={unionid}, userid={userid}")
            
            # 查找用户（优先用 unionid，因为 unionid 是跨应用唯一的）
            user = None
            
            # 方式1: 通过 dingtalk_unionid 查找
            if unionid:
                user = request.env['res.users'].sudo().search([
                    ('dingtalk_unionid', '=', unionid)
                ], limit=1)
                if user:
                    _logger.info(f"✅ 通过 unionid 找到用户: {user.login}")
            
            # 方式2: 通过 dingtalk_userid 查找
            if not user and userid:
                user = request.env['res.users'].sudo().search([
                    ('dingtalk_userid', '=', userid)
                ], limit=1)
                if user:
                    _logger.info(f"✅ 通过 userid 找到用户: {user.login}")
            
            # 方式3: 通过钉钉员工记录查找
            if not user:
                # 先尝试通过 unionid 查找员工
                employee = None
                if unionid:
                    employee = request.env['dingtalk.employee'].sudo().search([
                        ('unionid', '=', unionid)
                    ], limit=1)
                
                # 如果没找到，再用 userid 查找
                if not employee and userid:
                    employee = request.env['dingtalk.employee'].sudo().search([
                        ('userid', '=', userid)
                    ], limit=1)
                
                if employee and employee.user_id:
                    user = employee.user_id
                    # 更新用户的钉钉信息
                    update_vals = {}
                    if unionid:
                        update_vals['dingtalk_unionid'] = unionid
                    if userid:
                        update_vals['dingtalk_userid'] = userid
                    update_vals['dingtalk_employee_id'] = employee.id
                    
                    user.sudo().write(update_vals)
                    _logger.info(f"✅ 通过员工记录找到用户: {user.login}，并更新钉钉信息")
                else:
                    # 用户不存在
                    error_html = f"""
                    <!DOCTYPE html>
                    <html>
                    <head>
                        <meta charset="utf-8"/>
                        <title>钉钉登录失败</title>
                        <link rel="stylesheet" href="/web/static/lib/bootstrap/dist/css/bootstrap.min.css"/>
                    </head>
                    <body style="background: #f5f5f5; padding-top: 100px;">
                        <div class="container">
                            <div class="alert alert-warning text-center" style="max-width: 600px; margin: 0 auto; padding: 40px;">
                                <h3 style="color: #f0ad4e;">该钉钉账号尚未关联到系统用户</h3>
                                <hr/>
                                <p style="font-size: 16px;">
                                    您好，{user_info.get('nick', '用户')}！<br/><br/>
                                    您的钉钉账号尚未在系统中创建用户。<br/><br/>
                                    请联系管理员将您的钉钉账号转换为系统用户。
                                </p>
                                <div style="background: #f9f9f9; padding: 15px; margin: 20px 0; border-radius: 5px;">
                                    <p style="margin: 5px 0; font-size: 14px; color: #666;">
                                        <strong>UnionID:</strong> {unionid}<br/>
                                        <strong>邮箱:</strong> {user_info.get('email', '未提供')}
                                    </p>
                                </div>
                                <hr/>
                                <a href="/web/login" class="btn btn-primary btn-lg">返回登录页面</a>
                            </div>
                        </div>
                    </body>
                    </html>
                    """
                    return request.make_response(error_html, headers={'Content-Type': 'text/html'})
            
            # 3. 登录用户（钉钉 OAuth 登录，不需要密码）
            _logger.info(f"✅ 准备登录用户: {user.login}")
            
            # 在 Odoo 18 中，对于 OAuth 登录，需要正确设置会话
            request.session.uid = user.id
            request.session.login = user.login
            request.session.session_token = user._compute_session_token(request.session.sid)
            request.session.context = request.env['res.users'].context_get()
            
            # 更新会话数据库记录
            request.session.db = request.db
            
            _logger.info(f"✅ 登录成功！用户 ID: {user.id}, 登录名: {user.login}")
            
            # 4. 跳转到主页
            _logger.info(f"🏠 跳转到主页: /web")
            return request.redirect('/web')
            
        except Exception as e:
            _logger.error(f"❌ 回调处理失败: {str(e)}")
            import traceback
            _logger.error(traceback.format_exc())
            
            # 返回简单的HTML错误页面
            error_html = f"""
            <!DOCTYPE html>
            <html>
            <head>
                <meta charset="utf-8"/>
                <title>钉钉登录失败</title>
                <link rel="stylesheet" href="/web/static/lib/bootstrap/dist/css/bootstrap.min.css"/>
            </head>
            <body style="background: #f5f5f5; padding-top: 100px;">
                <div class="container">
                    <div class="alert alert-danger text-center" style="max-width: 600px; margin: 0 auto; padding: 40px;">
                        <h3 style="color: #d9534f;">钉钉登录失败</h3>
                        <hr/>
                        <p style="font-size: 16px; white-space: pre-line;">{str(e)}</p>
                        <hr/>
                        <a href="/web/login" class="btn btn-primary btn-lg">返回登录页面</a>
                    </div>
                </div>
            </body>
            </html>
            """
            return request.make_response(error_html, headers={'Content-Type': 'text/html'})
    
    def _get_user_info_by_code(self, auth_code, config):
        """通过授权码获取用户信息 - 使用新版 OAuth 2.0 API (2025)
        
        参考文档：https://open.dingtalk.com/document/orgapp/tutorial-obtaining-user-personal-information
        
        流程：
        1. 用 authCode 换取 accessToken (用户个人token)
        2. 用 accessToken 获取用户信息
        """
        try:
            # 步骤1: 用 authCode 换取 access_token (用户个人token)
            token_url = "https://api.dingtalk.com/v1.0/oauth2/userAccessToken"
            token_data = {
                "clientId": config.app_key,
                "clientSecret": config.app_secret,
                "code": auth_code,
                "grantType": "authorization_code"
            }
            
            _logger.info(f"📡 步骤1: 获取用户 access_token")
            _logger.info(f"   URL: {token_url}")
            _logger.info(f"   clientId: {config.app_key}")
            _logger.info(f"   code: {auth_code}")
            
            token_response = requests.post(token_url, json=token_data, timeout=10)
            token_result = token_response.json()
            
            _logger.info(f"📡 Token 响应: {token_result}")
            
            if 'accessToken' not in token_result:
                _logger.error(f"❌ 获取 access_token 失败: {token_result}")
                return None
            
            user_access_token = token_result['accessToken']
            _logger.info(f"✅ 获取到用户 access_token: {user_access_token[:20]}...")
            
            # 步骤2: 用 access_token 获取用户信息
            userinfo_url = "https://api.dingtalk.com/v1.0/contact/users/me"
            headers = {
                "x-acs-dingtalk-access-token": user_access_token,
                "Content-Type": "application/json"
            }
            
            _logger.info(f"📡 步骤2: 获取用户信息")
            _logger.info(f"   URL: {userinfo_url}")
            
            userinfo_response = requests.get(userinfo_url, headers=headers, timeout=10)
            userinfo_result = userinfo_response.json()
            
            _logger.info(f"📡 用户信息响应: {userinfo_result}")
            
            if 'unionId' in userinfo_result:
                # 获取到用户信息
                user_info = {
                    'unionid': userinfo_result.get('unionId'),
                    'nick': userinfo_result.get('nick'),
                    'openId': userinfo_result.get('openId'),
                }
                
                _logger.info(f"✅ 获取用户信息成功: {user_info}")
                
                # 步骤3: 通过 unionid 获取企业内的 userid
                if user_info['unionid']:
                    enterprise_token = config.get_valid_access_token()
                    detail = self._get_userid_by_unionid(user_info['unionid'], enterprise_token)
                    if detail:
                        user_info['userid'] = detail.get('userid')
                        user_info['name'] = detail.get('name')
                        _logger.info(f"✅ 获取到企业 userid: {user_info['userid']}")
                
                return user_info
            else:
                _logger.error(f"❌ 用户信息格式错误: {userinfo_result}")
                return None
                
        except Exception as e:
            _logger.error(f"❌ 获取用户信息异常: {str(e)}")
            import traceback
            _logger.error(traceback.format_exc())
            return None
    
    def _get_user_info_by_code_v2(self, auth_code, config):
        """方式2: 使用企业 access_token 换取用户信息"""
        try:
            access_token = config.get_valid_access_token()
            
            # 尝试用企业 access_token 直接获取
            url = "https://oapi.dingtalk.com/topapi/user/getuserinfo"
            params = {"access_token": access_token}
            data = {"code": auth_code}
            
            _logger.info(f"📡 请求用户信息 (方式2: access_token):")
            _logger.info(f"   URL: {url}")
            _logger.info(f"   code: {auth_code}")
            
            response = requests.post(url, params=params, json=data, timeout=10)
            result = response.json()
            
            _logger.info(f"📡 响应: {result}")
            
            if result.get('errcode') == 0:
                userid = result.get('result', {}).get('userid')
                if userid:
                    # 获取完整的用户信息
                    user_detail = self._get_user_detail(userid, access_token)
                    if user_detail:
                        return {
                            'userid': userid,
                            'unionid': user_detail.get('unionid'),
                            'name': user_detail.get('name'),
                        }
            
            return None
            
        except Exception as e:
            _logger.error(f"❌ 方式2失败: {str(e)}")
            return None
    
    def _get_user_detail(self, userid, access_token):
        """获取用户详细信息"""
        url = "https://oapi.dingtalk.com/topapi/v2/user/get"
        params = {"access_token": access_token}
        data = {"userid": userid}
        
        try:
            response = requests.post(url, params=params, json=data, timeout=10)
            result = response.json()
            
            if result.get('errcode') == 0:
                return result.get('result')
            return None
        except:
            return None
    
    def _get_userid_by_unionid(self, unionid, access_token):
        """通过 unionid 获取 userid"""
        url = "https://oapi.dingtalk.com/topapi/user/getbyunionid"
        params = {"access_token": access_token}
        data = {"unionid": unionid}
        
        try:
            _logger.info(f"📡 步骤3: 通过 unionid 获取企业 userid")
            _logger.info(f"   URL: {url}")
            _logger.info(f"   unionid: {unionid}")
            
            response = requests.post(url, params=params, json=data, timeout=10)
            result = response.json()
            
            _logger.info(f"📡 响应: {result}")
            
            if result.get('errcode') == 0:
                user_detail = result.get('result', {})
                _logger.info(f"✅ 获取到企业用户信息: {user_detail}")
                return user_detail
            else:
                _logger.error(f"❌ 通过 unionid 获取 userid 失败: {result.get('errmsg')}")
                return None
        except Exception as e:
            _logger.error(f"❌ 获取 userid 异常: {str(e)}")
            import traceback
            _logger.error(traceback.format_exc())
            return None
    
    def _compute_signature(self, timestamp, app_secret):
        """计算钉钉扫码登录签名
        
        参考官方文档：
        https://open.dingtalk.com/document/orgapp/obtain-the-user-information-based-on-the-sns-temporary-authorization
        
        签名算法（免登场景）：
        stringToSign = timestamp
        signature = Base64(HMAC-SHA256(stringToSign, appSecret))
        
        注意：扫码登录的签名只用 timestamp，不包含 appSecret
        """
        import hmac
        import hashlib
        import base64
        import urllib.parse
        
        # 根据官方文档：签名字符串只是 timestamp（不包含其他内容）
        string_to_sign = timestamp
        
        # 使用 AppSecret 作为密钥进行 HMAC-SHA256
        hmac_code = hmac.new(
            app_secret.encode('utf-8'),
            string_to_sign.encode('utf-8'),
            digestmod=hashlib.sha256
        ).digest()
        
        # Base64 编码
        signature = base64.b64encode(hmac_code).decode('utf-8')
        
        # URL 编码（注意：不要过度编码）
        signature_encoded = urllib.parse.quote(signature, safe='')
        
        _logger.info(f"🔐 签名计算 (官方算法):")
        _logger.info(f"   timestamp: {timestamp}")
        _logger.info(f"   string_to_sign: '{string_to_sign}' (只有timestamp)")
        _logger.info(f"   signature (base64): {signature}")
        _logger.info(f"   signature (urlencoded): {signature_encoded}")
        
        return signature_encoded


class WebLogin(Home):
    """扩展登录页面，添加钉钉登录按钮"""
    
    @http.route()
    def web_login(self, *args, **kw):
        """覆盖登录方法，添加钉钉登录选项"""
        response = super(WebLogin, self).web_login(*args, **kw)
        
        # 检查是否配置了钉钉
        config = request.env['dingtalk.config'].sudo().search([('active', '=', True)], limit=1)
        
        if config and hasattr(response, 'qcontext'):
            response.qcontext['dingtalk_enabled'] = True
            response.qcontext['dingtalk_login_url'] = '/auth/dingtalk'
        
        return response

